/* NOTICE
    OpenJaWS - Open Java Weather Station
    
    Copyright (C) 2008 Grant Gardner <grant@lastweekend.com.au>
    
    OpenJaWS is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
NOTICE */

package au.com.lastweekend.openjaws.api;

import java.io.Serializable;
import java.util.Date;

import au.com.lastweekend.openjaws.units.Unit;

/**
 * Structure to encapsulate a current, minimum and maximum value
 * 
 * After initialising max and min values further calls to set current that supply a reading time will update max/min if necessary.
 * 
 * Will not return null values.
 * 
 * @author ggardner
 * 
 */
public class CurrentMinMax implements Serializable {

    private UnitValue current = UnitValue.NULL;
    private RecordedValue min = RecordedValue.NULL;
    private RecordedValue max = RecordedValue.NULL;
    private transient boolean isDirty;

    public CurrentMinMax() {

        this(null);
    }

    public CurrentMinMax(CurrentMinMax obj) {

        super();
        if (obj != null) {
            copy(obj);
        }
    }

    public void copy(CurrentMinMax other) {

        this.current = other.current;
        this.min = other.min;
        this.max = other.max;
        this.isDirty = other.isDirty;
    }

    public void average(CurrentMinMax next, long totalTime, long timeDiff, Date readingTime) {

        double currentValue = current.getValue();
        Unit currentUnit = current.getUnit();

        // convert to same unit as current
        UnitValue nextUnitValue = next.current.convertTo(currentUnit);
        double nextValue = nextUnitValue.getValue();
        double accumValue = (currentValue * totalTime) + (timeDiff * nextValue);
        double newValue = accumValue / (totalTime + timeDiff);

        // First set the actual reading so we keep the true min/max
        setCurrent(nextValue, currentUnit, readingTime);
        // Now store the new average
        setCurrent(newValue, currentUnit);
    }

    public void setCurrent(double current, Unit unit) {

        this.setCurrent(current, unit, null);

    }

    public void setCurrent(double current, Unit unit, Date readingTime) {

        setCurrent(new UnitValue(current, unit), readingTime);

    }

    public void setMinimum(double minimum, Unit unit, Date timeMinimum) {

        this.min = new RecordedValue(timeMinimum, minimum, unit);
    }

    public void setMaximum(double maximum, Unit unit, Date timeMaximum) {

        this.max = new RecordedValue(timeMaximum, maximum, unit);
    }

    public UnitValue getCurrent() {

        return protectNull(current);
    }

    public RecordedValue getMin() {

        return protectNull(min);
    }

    public RecordedValue getMax() {

        return protectNull(max);
    }

    protected void setMinimum(UnitValue min, Date timeMin) {

        this.min = new RecordedValue(timeMin, protectNull(min));
    }

    protected void setMaximum(UnitValue max, Date timeMax) {

        this.max = new RecordedValue(timeMax, protectNull(max));
    }

    @Override
    public int hashCode() {

        final int prime = 31;
        int result = 1;
        result = prime * result + ((current == null) ? 0 : current.hashCode());
        result = prime * result + ((max == null) ? 0 : max.hashCode());
        result = prime * result + ((min == null) ? 0 : min.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {

        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final CurrentMinMax other = (CurrentMinMax) obj;
        if (current == null) {
            if (other.current != null)
                return false;
        } else if (!current.equals(other.current))
            return false;
        if (max == null) {
            if (other.max != null)
                return false;
        } else if (!max.equals(other.max))
            return false;
        if (min == null) {
            if (other.min != null)
                return false;
        } else if (!min.equals(other.min))
            return false;
        return true;
    }

    protected void setCurrent(UnitValue argCurrent, Date readingTime) {

        argCurrent = protectNull(argCurrent);

        if (this.current.equals(argCurrent)) {
            // Nothing to do.
            return;
        }
        setDirty(true);
        this.current = argCurrent;

        if (readingTime != null) {
            if (min.getUnitValue().isNull() || this.current.compareTo(min.getUnitValue()) < 0) {
                setMinimum(argCurrent, readingTime);
            }
            if (max.getUnitValue().isNull() || this.current.compareTo(max.getUnitValue()) > 0) {
                setMaximum(argCurrent, readingTime);
            }
        }
    }

    public boolean isDirty() {

        return isDirty;
    }

    public void clean() {

        isDirty = false;
    }

    private void setDirty(boolean isDirty) {

        this.isDirty = isDirty;

    }

    protected UnitValue protectNull(UnitValue uv) {

        if (uv == null) {
            return UnitValue.NULL;
        }
        return uv;
    }

    protected RecordedValue protectNull(RecordedValue rv) {

        if (rv == null) {
            return RecordedValue.NULL;
        } else {
            return rv;
        }
    }

    public String toString() {

        return String.format("Current = %s Min = %s, Max = %s", getCurrent(), getMin(), getMax());
    }

}